// --- Copyright (c) 2010 Steven Hartgers ---
// Copyright (C) 2010 Steven Hartgers All rights are reserved.
// Author contact: fiinix00/at\gmail.com
// ------------------------------------------------------------------------
// This program is free software: you can redistribute it and/or modify 
// it under the terms of the GNU General Public License as published by 
// the Free Software Foundation, either version 3 of the License, or 
// (at your option) any later version. 
//  
// This program is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied warranty of 
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
// GNU General Public License for more detail
//  
// You should have received a copy of the GNU General Public License 
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
// ------------------------------------------------------------------------
// COPYRIGHT NOTICE: This copyright notice may NOT be removed, obscured or modified 
// without written consent from the author.
// --- End copyright notice --- 




using System;
using System.IO;
using System.Collections;
using System.Net.Sockets;
using System.Threading;

namespace Hack.Net.Smtp
{
    public class SmtpProxy
    {
        /** SMTP proxy properties */
        private readonly SmtpProxyProperties prop;

        /** Size of thread pool to forward mails */
        private readonly int poolSize;

        /** The SMTP server */
        private readonly SmtpServer server;

        /** Consumer producer queue */
        private readonly CPQueue queue;

        /** The pool of sending threads */
        private ArrayList workerThreads = new ArrayList();



        /** Create a new SMTP server */
        public SmtpProxy(SmtpProxyProperties proxyprop, SmtpProperties smtpprop, int poolSize, int maxConn)
        {
            this.queue = new CPQueue(poolSize);
            this.poolSize = poolSize;
            this.prop = proxyprop;


            //Create SMTP server
            this.server = new SmtpServer(smtpprop, poolSize, maxConn);
            //Register hanlder for received SMTP request
            this.server.smtpReceived += new SmtpServer.ReceivingHandler(OnSmtpReceived);
        }


        /** Start proxy */
        public void Start()
        {
            //Start the SMTP Server
            server.Start();

            //Creating and starting threads
            for (int i = 0; i < poolSize; i++)
            {
                SenderWorker w = new SenderWorker(prop, queue);
                Thread th = new Thread(new ThreadStart(w.sendMail));
                workerThreads.Add(th);

                th.Start();
            }
        }

        /** Stop proxy */
        public void Stop()
        {
            //Stop the SMTP Server
            server.Stop();

            //Abort all worker threads in pool...
            foreach (Thread th in workerThreads)
                th.Abort();

            //...and wait for their termination
            foreach (Thread th in workerThreads)
                th.Join();

            //Clear the list
            workerThreads.Clear();
        }


        /** Received new mail */
        private void OnSmtpReceived(SmtpReceiving recv)
        {
            //Enqueue the received request
            queue.Enqueue(recv);
        }

        /** Display help message */
        private static void DisplayHelp()
        {
            System.Console.WriteLine("This is a simple SMTP Proxy.");
            System.Console.WriteLine("");
            System.Console.WriteLine("Usage:");
            System.Console.WriteLine("   <binary> [--myhost host] [--port port] [--log-only] [--log] [--forward host] [--forward-port port] [--help]");
            System.Console.WriteLine("");
            System.Console.WriteLine("Hit return to quit...");
        }

        /** Execute the smtp server */
        public static void Main(string[] args)
        {
            SmtpProxyProperties prop = new SmtpProxyProperties("localhost");

            //Create properties struct for server
            SmtpProperties smtpprop = new SmtpProperties();
            smtpprop.greetings = "mail.mynewbestpart.org My Simple SMTP Server";


            //Parse CLI arguments
            for (int idx = 0; idx < args.Length; idx++)
            {
                if (args[idx].Equals("--log-only"))
                    prop.logonly = true;

                else if (args[idx].Equals("--myhost") && idx < args.Length - 1)
                    prop.myhost = args[++idx];

                else if (args[idx].Equals("--log"))
                    prop.log = true;

                else if (args[idx].Equals("--help"))
                    DisplayHelp();

                else if (args[idx].Equals("--forward") && idx < args.Length - 1)
                    prop.forwardhost = args[++idx];

                else if (args[idx].Equals("--forward-port") && idx < args.Length - 1)
                {
                    try
                    {
                        prop.forwardport = int.Parse(args[++idx]);
                    }
                    catch (FormatException)
                    {
                        System.Console.WriteLine("Invalid port!");
                        System.Environment.Exit(-1);
                    }

                }

                else if (args[idx].Equals("--port") && idx < args.Length - 1)
                {
                    try
                    {
                        smtpprop.port = int.Parse(args[++idx]);
                    }
                    catch (FormatException)
                    {
                        System.Console.WriteLine("Invalid port!");
                        System.Environment.Exit(-1);
                    }
                }

                else
                {
                    System.Console.WriteLine("Invalid option: " + args[idx]);
                    System.Console.WriteLine("");
                    DisplayHelp();
                    System.Environment.Exit(-1);
                }
            }


            System.Console.WriteLine("");
            System.Console.WriteLine("Press return to quit...\n");



            //Create the server and start it with specific properties
            SmtpProxy p = new SmtpProxy(prop, smtpprop, 10, 10);

            p.Start();
            //Wait for return and stop server
            System.Console.Read();
            p.Stop();
        }
    }


    /** Properties of SMTP Proxy */
    public class SmtpProxyProperties
    {
        /** Log every incoming mail */
        public bool log = false;

        /** Only log incoming mail, no forwarding */
        public bool logonly = false;

        /** Forward all mails to specific host */
        public string forwardhost = null;

        /** Forward port to connect to */
        public int forwardport = 25;

        /** The hostname of localhost */
        public string myhost;


        /** Create proxy properties */
        public SmtpProxyProperties(string myhost)
        {
            this.myhost = myhost;
        }
    }


    /** This worker sends all SMTP messages received */
    internal class SenderWorker
    {
        /** Queue which contains the messages */
        private readonly CPQueue queue;

        /** Properties of SMTP proxy */
        private readonly SmtpProxyProperties prop;


        /** Create new sender */
        public SenderWorker(SmtpProxyProperties prop, CPQueue queue)
        {
            this.queue = queue;
            this.prop = prop;
        }

        /** Send new mail */
        public void sendMail()
        {
            //Send all received queues
            while (true)
            {
                //Get new SMTP receiving
                SmtpReceiving recv = (SmtpReceiving)queue.Dequeue();

                //Log the incoming mail
                if (prop.logonly || prop.log)
                    Log(recv);

                //Send the mail
                if (!prop.logonly)
                {
                    //Create sending object
                    SmtpSending s = new SmtpSending(prop.myhost,
                            recv.MailFromReversePath,
                            recv.MailFromParameters,
                            recv.RcpToForwardPath,
                            recv.RcpToParameters,
                            recv.Data);

                    try
                    {
                        //Send the mail
                        if (prop.forwardhost != null)
                            s.Send(prop.forwardhost, prop.forwardport);
                        else
                            s.Send(recv.RcpToForwardHost, prop.forwardport);
                    }
                    catch (SocketException)
                    {
                        System.Console.WriteLine("Error when sending mail.");
                    }
                    catch (IOException)
                    {
                        System.Console.WriteLine("Error when sending mail.");
                    }
                }
            }
        }


        /** Log the mail */
        private void Log(SmtpReceiving recv)
        {
            System.Console.WriteLine("Received mail from " + recv.MailFromReversePath +
                            " to " + recv.RcpToForwardPath + " (" + recv.RcpToForwardHost + ")");
            System.Console.WriteLine("Data:" + recv.Data);
        }
    }



}